package com.basker.pisces.core.query.builder;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import com.basker.pisces.core.enums.AggregateType;
import com.basker.pisces.core.query.AggregateField;
import com.basker.pisces.core.query.GroupField;
import com.basker.pisces.core.query.OrderField;
import com.basker.pisces.core.query.QueryInfo;
import com.basker.pisces.core.query.SelectField;
import com.basker.pisces.core.query.criteria.Criterion;
import com.basker.pisces.core.query.meta.FieldDesc;
import com.basker.pisces.core.query.meta.FieldDescCollection;
import com.basker.pisces.core.query.meta.QueryMeta;
import com.basker.pisces.core.query.meta.ReferenceFieldDesc;

/**
 * 构建{@link QueryInfo}的工具
 *
 * @author hangwen
 */
public class QueryBuilder {

    /**
     * 创建构建器，需要传入一个{@link QueryMeta}
     *
     * @param queryMeta
     * @return
     */
    public static QueryBuilder create(QueryMeta queryMeta) {
        return new QueryBuilder(queryMeta);
    }

    private QueryMeta queryMeta;
    private boolean selectReferenceFields = false;
    private QueryInfo queryInfo = new QueryInfo();

    private QueryBuilder(QueryMeta queryMeta) {
        this.queryMeta = queryMeta;
    }

    /**
     * 添加已经构建完成的条件,criteria可以从别的{@link QueryInfo}获取
     *
     * @param criteria
     * @return
     */
    public QueryBuilder addCriteria(List<Criterion> criteria) {
        if (CollectionUtils.isEmpty(criteria)) {
            return this;
        }

        for (Criterion criterion : criteria) {
            this.queryInfo.addCriterion(criterion);
        }

        return this;
    }

    /**
     * 创建聚合查询字段
     *
     * @param type
     * @param fieldIds
     * @return
     */
    public QueryBuilder aggregate(AggregateType type, String... fieldIds) {
        Assert.notNull(type, "parameter 'type' is required");

        if (ObjectUtils.isEmpty(fieldIds)) {
            return this;
        }

        List<FieldDesc> fields = this.findFields(fieldIds);

        for (FieldDesc field : fields) {
            this.aggregate0(type, field);
        }

        return this;
    }

    /**
     * 创建查询条件，多个条件之间用and拼接
     *
     * @param builders
     * @return
     */
    public QueryBuilder andCriteria(CriterionBuilder... builders) {
        return this.criteria(Criterion.AND, builders);
    }

    /**
     * 构建查询条件
     *
     * @return
     */
    public List<Criterion> buildCriteria() {
        return this.queryInfo.getCriteria();
    }

    /**
     * 构建QueryInfo
     *
     * @return
     */
    public QueryInfo buildQueryInfo() {
        return this.queryInfo;
    }

    public QueryBuilder groupBy(boolean asSelectField, String... fieldIds) {
        if (ObjectUtils.isEmpty(fieldIds)) {
            return this;
        }

        List<FieldDesc> fields = this.findFields(fieldIds);

        for (FieldDesc field : fields) {
            this.groupBy(field, asSelectField);
        }

        return this;
    }

    /**
     * 是否需要查询出引用字段的关联字段
     *
     * @return
     */
    public boolean isSelectReferenceFields() {
        return selectReferenceFields;
    }

    /**
     * 创建查询条件，多个条件之间用or拼接
     *
     * @param builders
     * @return
     */
    public QueryBuilder orCriteria(CriterionBuilder... builders) {
        return this.criteria(Criterion.OR, builders);
    }

    /**
     * 创建排序字段，默认升序
     *
     * @param field
     * @return
     */
    public QueryBuilder orderBy(String field) {
        return this.orderBy(field, false);
    }

    /**
     * 创建排序字段
     *
     * @param fieldId
     * @param isDesc
     * @return
     */
    public QueryBuilder orderBy(String fieldId, boolean isDesc) {
        Assert.hasText(fieldId, "parameter 'fieldId' is required");

        FieldDesc field = this.queryMeta.getField(fieldId, true);

        OrderField orderField = new OrderField(field.getId(), isDesc ? OrderField.DESC : OrderField.ASC);
        this.queryInfo.addOrderField(orderField);

        return this;
    }

    /**
     * 添加查询字段
     *
     * @param fields
     * @return
     */
    public QueryBuilder select(String... fieldIds) {
        if (ObjectUtils.isEmpty(fieldIds)) {
            return this;
        }

        List<FieldDesc> fields = this.findFields(fieldIds);

        for (FieldDesc field : fields) {
            this.select0(field);
        }

        return this;
    }

    /**
     * 添加主表字段
     *
     * @return
     */
    public QueryBuilder selectMainTableFields() {
        FieldDescCollection fields = this.queryMeta.getMainTable().getFields();

        for (FieldDesc field : fields) {
            this.select0(field);
        }

        return this;
    }

    public void setSelectReferenceFields(boolean selectReferenceFields) {
        this.selectReferenceFields = selectReferenceFields;
    }

    private void aggregate0(AggregateType type, FieldDesc field) {
        SelectField selectField = new AggregateField(field.getId(), field.getAlias(), type);
        this.queryInfo.addSelectField(selectField);
    }

    private QueryBuilder criteria(String andOr, CriterionBuilder... builders) {
        if (ObjectUtils.isEmpty(builders)) {
            return this;
        }

        List<Criterion> criteria = this.queryInfo.getCriteria();
        if (!CollectionUtils.isEmpty(criteria)) {
            criteria.get(criteria.size() - 1).setAndOr(andOr);
        }

        for (CriterionBuilder builder : builders) {
            Criterion criterion = builder.createCriterion(queryMeta);

            if (criterion != null) {
                criterion.setAndOr(andOr);
                this.queryInfo.addCriterion(criterion);
            }
        }

        return this;
    }

    private List<FieldDesc> findFields(String... fieldIds) {
        String[] fieldIdArray = Stream.of(fieldIds).distinct().toArray(l -> new String[l]);
        List<FieldDesc> fields = new ArrayList<>(fieldIdArray.length);

        for (String fieldId : fieldIdArray) {
            FieldDesc field = this.queryMeta.getField(fieldId, true);
            fields.add(field);
        }

        return fields;
    }

    private void groupBy(FieldDesc field, boolean asSelectField) {
        this.queryInfo.addGroupByField(new GroupField(field.getId()));

        if (asSelectField) {
            this.select0(field);
        }
    }

    private void select0(FieldDesc field) {
        SelectField selectField = new SelectField(field.getId(), field.getAlias());
        this.queryInfo.addSelectField(selectField);

        if (this.isSelectReferenceFields() && field instanceof ReferenceFieldDesc) {
            this.selectReferenceFields((ReferenceFieldDesc) field);
        }
    }

    private void selectReferenceFields(ReferenceFieldDesc referenceFieldDesc) {
        FieldDesc refCodeField = referenceFieldDesc.getReferencedCodeField();
        if (refCodeField != null) {
            this.select0(refCodeField);
        }

        FieldDesc refNameField = referenceFieldDesc.getReferencedNameField();
        if (refNameField != null) {
            this.select0(refNameField);
        }

        for (FieldDesc refField : referenceFieldDesc.getOtherReferencedFields()) {
            this.select0(refField);
        }
    }

}
